package org.xx.armory.rpc.routing;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.Attributes;
import org.xx.armory.objectparser.LineInfo;
import org.xx.armory.objectparser.ParserException;
import org.xx.armory.objectparser.impl.XMLFileParser;
import org.xx.armory.rpc.RpcVerb;

import java.util.Collection;

import static org.xx.armory.xml.AttributesHelper.getBoolean;
import static org.xx.armory.xml.AttributesHelper.getString;

/**
 * XML路由表文件解析器。
 *
 * @author Haart
 */
public final class XmlRouteFileParser
        extends XMLFileParser<Route> {
    private static final String XSD_FILE_NAME = "classpath://armory-routes.xsd";
    private final Logger logger = LoggerFactory.getLogger(XmlRouteFileParser.class);

    /**
     * 构造{@link XmlRouteFileParser}类型的实例。
     */
    public XmlRouteFileParser() {
        setSchema(XSD_FILE_NAME);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected ElementHandler getHandler(
            final Collection<? super Route> c
    ) {
        return new ElementHandler() {
            private static final int DEFAULTS_PARAM = 1;
            private static final int RESTRICTIONS_PARAM = 2;

            private String prefix = "";

            private int paramType = 0;
            private String paramName = "";
            private String paramValue = "";

            @Override
            public void startElement(
                    LineInfo lineInfo,
                    String name,
                    Attributes attributes
            ) {
                if ("root".equals(name)) {
                    // 发现根节点。
                } else if ("module".equals(name)) {
                    prefix = getString(attributes, "prefix");

                    logger.trace("--- BEGIN PREFIX [{}] ---", prefix);
                } else if ("route".equals(name)) {
                    enterRoute(lineInfo, attributes, prefix);
                } else if ("defaults".equals(name)) {
                    paramType = DEFAULTS_PARAM;
                } else if ("restrictions".equals(name)) {
                    paramType = RESTRICTIONS_PARAM;
                } else if ("param".equals(name)) {
                    paramName = getString(attributes, "name");
                    paramValue = getString(attributes, "value");
                } else {
                    throw new ParserException("unknown element \"" + name + "\" at " + lineInfo);
                }
            }

            @Override
            public void text(
                    LineInfo lineInfo,
                    String text
            ) {
            }

            @Override
            public void endElement(
                    LineInfo lineInfo,
                    String name
            ) {
                if ("root".equals(name)) {
                    // 不处理根节点的结束标签。
                } else if ("module".equals(name)) {
                    final String oldPrefix = prefix;
                    prefix = "";

                    logger.trace("---- END PREFIX [" + oldPrefix + "] ----");
                } else if ("route".equals(name)) {
                    final Route route = exitRoute();
                    if (route.isEnabled()) {
                        c.add(route);
                    } else {
                        logger.trace("skip disabled route: {}", route.getPattern());
                    }
                } else if ("defaults".equals(name)) {
                    paramType = 0;
                } else if ("restrictions".equals(name)) {
                    paramType = 0;
                } else if ("param".equals(name)) {
                    if (paramType == DEFAULTS_PARAM) {
                        ((RouteBuilder) peek()).addDefault(paramName, paramValue);
                    } else if (paramType == RESTRICTIONS_PARAM) {
                        ((RouteBuilder) peek()).addRestriction(paramName, paramValue);
                    }
                }
            }

        };
    }

    /**
     * 加载&lt;route&gt;元素。
     *
     * @param lineInfo
     *         位置信息。
     * @param attributes
     *         元素的属性集合。
     * @param prefix
     *         路由的前缀。
     */
    private void enterRoute(
            LineInfo lineInfo,
            Attributes attributes,
            String prefix
    ) {
        final RouteBuilder builder = new RouteBuilder();

        final String pattern = getString(attributes, "pattern");

        push(builder.setPattern(prefix.isEmpty() ? pattern : prefix + pattern)
                    .setClassName(getString(attributes, "class"))
                    .setMethodName(getString(attributes, "method", "execute"))
                    .setVerb(getVerb(attributes, "verb"))
                    .setPermissions(getString(attributes, "perms"))
                    .setEnabled(getBoolean(attributes, "enabled", true))
                    .setDescription(getString(attributes, "description"))
                    .setLineInfo(lineInfo));

        logger.trace("parsing route \"{}\"...", pattern);
    }

    /**
     * 完成加载&lt;route&gt;元素。
     *
     * @return 已加载的路由对象。
     */
    private Route exitRoute() {
        final Route r = ((RouteBuilder) pop()).build();

        logger.trace("parsed route \"{}\".", r.getPattern());

        return r;
    }

    /**
     * 从属性集合中获取请求方法。
     *
     * @param attributes
     *         属性集合。
     * @param key
     *         请求方法的键名。
     * @return 请求方法的值。
     */
    private RpcVerb getVerb(
            Attributes attributes,
            String key
    ) {
        final String s = getString(attributes, key, "POST");

        if ("GET".equalsIgnoreCase(s)) {
            return RpcVerb.GET;
        } else if ("POST".equalsIgnoreCase(s)) {
            return RpcVerb.POST;
        } else if ("PUT".equalsIgnoreCase(s)) {
            return RpcVerb.PUT;
        } else if ("DELETE".equalsIgnoreCase(s)) {
            return RpcVerb.DELETE;
        } else if ("HEAD".equalsIgnoreCase(s)) {
            return RpcVerb.HEAD;
        } else {
            return RpcVerb.UNKNOWN;
        }
    }
}
